package cloudspace.vm.io.console;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cloudspace.vm.VM;

/**
 * This class holds all of the registered streams and creates a master stream
 * out of them. Individual streams are registered and the Console then
 * aggregates their stream lines into one sorted output.
 */
public class Console
{

	private class LoggedInfoStream
	{
		long currentLineNumber;
		InfoStream iStream;

		public LoggedInfoStream(InfoStream infoStream)
		{
			iStream = infoStream;
			currentLineNumber = iStream.getLineCacheStart();
		}

		public InfoStream getInfoStream()
		{
			return iStream;
		}

		public List<InfoStreamLine> getNewLines()
		{
			synchronized (iStream)
			{
				try
				{
					List<InfoStreamLine> lines = iStream
							.getLines(currentLineNumber);
					currentLineNumber = iStream.getLineNumber();
					return lines;
				}
				catch (FlushedException e)
				{
					currentLineNumber = iStream.getLineCacheStart();
					return getNewLines();
				}

			}
		}

		public boolean isDirty()
		{
			synchronized (iStream)
			{
				return iStream.getLineNumber() > currentLineNumber;
			}
		}
	}

	/**
	 * This class compares two different InfoStreamLines. First looking at the
	 * time stamps then looking at line numbers. However, line numbers are only
	 * used when the timestamps are the same and both info stream lines come
	 * from the same streamID.
	 */
	public class InfoStreamLineComparator implements Comparator<InfoStreamLine>
	{

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		@Override
		public int compare(InfoStreamLine o1, InfoStreamLine o2)
		{
			if (o1.getTimestamp() < o2.getTimestamp())
			{
				return -1;
			}
			else if (o1.getTimestamp() > o2.getTimestamp())
			{
				return 1;
			}
			else if (o1.getStreamID().equals(o2.getStreamID()))
			{
				if (o1.getLineNumber() > o2.getLineNumber())
				{
					return 1;
				}
				else
				{
					return -1;
				}
			}
			return 0;
		}

	}

	/** The id stream map. */
	private Map<String, LoggedInfoStream> idStreamMap;
	/**
	 * Instantiates a new stream provider.
	 */
	public Console()
	{
		idStreamMap = new HashMap<String, LoggedInfoStream>();
		init();
	}

	/**
	 * Create a console with both a standard out and error stream. If you want
	 * standard out and error to be registered to this console, you MUST use
	 * this method.
	 * 
	 * @param stdOut
	 * @param error
	 */
	public Console(InfoStream stdOut, InfoStream error)
	{
		this();
		registerInfoStream(stdOut);
		registerInfoStream(error);
		init();
	}
	public void init()
	{
		registerInfoStream(VM.getThreadTag().getLog());
		
	}

	/**
	 * Gets all of the new lines stored in the console that need to be printed
	 * out.
	 * 
	 * @return a set of new lines in descending order.
	 * 
	 * @throws Exception
	 *             This throws an exception if there is any problem accessing
	 *             the new infostreams. This is important for infostreams based
	 *             off of files and such.
	 */
	public List<InfoStreamLine> getNewOrderedLines()
	{
		List<InfoStreamLine> orderedNewLines = new LinkedList<InfoStreamLine>();
		Set<String> mapKeys = idStreamMap.keySet();
		Iterator<String> keyIter = mapKeys.iterator();
		while (keyIter.hasNext())
		{
			LoggedInfoStream curStream = idStreamMap.get(keyIter.next());
			if (curStream.isDirty())
				orderedNewLines.addAll(curStream.getNewLines());
			// orderedNewLines.addAll(tempNewLines);
		}
		Collections.sort(orderedNewLines, new InfoStreamLineComparator());
		return orderedNewLines;
	}

	/**
	 * Checks if any of the streams in this console are dirty.
	 * 
	 * @return true, if any of the streams are dirty
	 */
	public boolean isDirty()
	{
		Set<String> mapKeys = idStreamMap.keySet();
		Iterator<String> keyIter = mapKeys.iterator();
		while (keyIter.hasNext())
		{
			LoggedInfoStream curStream = idStreamMap.get(keyIter.next());
			if (curStream.isDirty())
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * Get all of the registered stream ids.
	 * 
	 * @return all of the stream ids registered to this console.
	 */
	public Iterator<String> getStreamIDs()
	{
		return idStreamMap.keySet().iterator();
	}

	/**
	 * Register an info stream to the current console. It must not have a stream
	 * id matching one of the protected ids such as system out and system error.
	 * 
	 * @param iStream
	 *            The info stream to log
	 * @param id
	 *            The id for the stream to be logged
	 */
	public void registerInfoStream(InfoStream iStream)
	{
			idStreamMap.put(iStream.getStreamID(),
					new LoggedInfoStream(iStream));

	}

	/**
	 * Gets the system out stream. This is one of the protected streams of the
	 * console.
	 * 
	 * @return the system out stream
	 */
	public InfoStream getSystemOutStream()
	{
		return idStreamMap.get(VM.standardOutTag).getInfoStream();
	}

	/**
	 * Gets the system error stream. this is one of the protected streams of the
	 * console.
	 * 
	 * @return the system error stream
	 */
	public InfoStream getSystemErrorStream()
	{
		return idStreamMap.get(VM.standardErrorTag).getInfoStream();
	}
	public InfoStream getSystemLogStream()
	{
		return idStreamMap.get(VM.logTag).getInfoStream();
	}

}
